/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */

package org.griphyn.vdl.directive;

import java.io.*;
import org.griphyn.vdl.directive.WorkflowJob;
import org.griphyn.vdl.util.*;
import java.sql.SQLException;

/**
 *
 * A <code>Workflow</code> object defines a context for running a
 * derivation graph on the Grid as a DAG, and managing its execution.
 * It serves as a front-end to an associated shell script (by default
 * located in $PEGASUS_HOME/bin/vds-Workflow-script-runwf)
 *
 * The workflow to be executed is designated by its terminal derivation (DV).
 *
 * The Workflow instance is returned by the class method Workflow.run().
 *
 * The Workflow class variables contain status cached from period
 * queries of the workflow database.
 *
 * @author Douglas Scheftner
 * @author Mike Wilde
 * @author Eric Gilbert
 * @version $Revision: 50 $
 *
 */

public class Workflow {

    /* Class Variables */


    public static String runwfCmd = "/home/dscheftn/vds/bin/vds-Workflow-script-runwf";
    public static String wfstatCmd = "/home/dscheftn/vds/bin/vds-Workflow-script-wfstat";

    public static String defaultVOGroup = "quarknet";

    public static String logicalFileNameBase = "/export/d1/dscheftn/quarknet_testing/runs";

    /** parent directory for run-dir tree ala vds-plan/vds-run. */
    public static String defaultBaseDir = "/no/base/dir";

    public static String rlsURL = "rls://terminable.uchicago.edu";

    public static final int MAXWF = 100000;
    public static Workflow[] workflows = new Workflow[MAXWF];
    public static int nworkflows;

    public static long millisecsToRefreshStatus = 30000; // 30 secs between auto-refresh of status
    public static long timeOfLastRefresh = 0; // really want this publically read-only

    //    private static String voGroup;

    /* Instance Variables */

    /* sample data from database:

     id |     basedir     | vogroup |  workflow  |   run   | creator |
    ----+-----------------+---------+------------+---------+---------+
      1 | /home/wilde/run | ivdgl1  | test       | run0001 | wilde   |

             ctime          | state |         mtime          
    ------------------------+-------+------------------------
     2005-08-20 13:25:27-05 |    -2 | 2005-08-20 13:28:09-05

    */
  
    /* Instance variables that mirror the database fields */
  
    public String wfid;   
    public String basedir;
    public String vogroup;
    public String wfname;
    public String run;
    public String creator;
    public String ctime;
    public String state;
    public String exitstatus;
    public String mtime;

    /* Instance variables to track workflow state */

    public static final int WFMAXJOBS = 20000; /* FIX: can we avoid this hard limit? */
    public WorkflowJob[] jobs;
    public int njobs;
    public String tmpdir;
    public String errorMessage;

    /* Class Methods */

    public static Workflow run ( String namespace, String dvName )
    {
	Process p;
	int rc;
	Reader is;
	StringBuffer sb = new StringBuffer();
	char [] b = new char[100000];
	int n;

	Workflow wf = new Workflow();

	wf.basedir = defaultBaseDir;
	wf.vogroup = defaultVOGroup;
	wf.wfname = dvName;

	try {
	    System.out.println("Running Process " + namespace + " " + dvName);

	    String[] cmd = { runwfCmd, defaultVOGroup, logicalFileNameBase, defaultBaseDir,
			     rlsURL, namespace, dvName };

	    p = Runtime.getRuntime().exec( cmd );

	    InputStream out = p.getInputStream();
	    InputStreamReader r = new InputStreamReader(out);
	    BufferedReader in = new BufferedReader(r);

	    wf.tmpdir = in.readLine();
	    System.out.println("output from runwf: tmpdir=" + wf.tmpdir);
	    wf.run = in.readLine();
	    System.out.println("output from runwf: run=" + wf.run);
	    wf.errorMessage = in.readLine();
	    System.out.println("output from runwf: errorMessage=" + wf.errorMessage);

	    rc = p.waitFor();
	    System.out.println("Process returned rc=" + rc);

	    return(wf);
	} catch (Exception e) {
	    System.out.println("Prepare: Exception: " + e.toString() );
	    return wf;
	}
    }
    
    public static boolean refresh()
    {
        Process p;
        int rc;
        Reader is;
        StringBuffer sb = new StringBuffer();
        char [] b = new char[100000];
        int n;
	
        /* Run status command to get workflow states */
	
	try {

	    p = Runtime.getRuntime().exec(wfstatCmd);
	    
	    InputStream out = p.getInputStream();
	    InputStreamReader r = new InputStreamReader(out);
	    BufferedReader in = new BufferedReader(r);
	    
	    String line;
	    nworkflows=0;
	    while ( (line = in.readLine()) != null ) {

		Workflow w = new Workflow();
		String[] t = line.split("\\|");
		int nt = t.length;
		if (nt > 1) w.wfid=t[1];
		if (nt > 2) w.basedir = t[2];
		if (nt > 3) w.vogroup = t[3];
		if (nt > 4) w.wfname = t[4];
		if (nt > 5) w.run = t[5];
		if (nt > 6) w.creator = t[6];
		if (nt > 7) w.ctime = t[7];
		if (nt > 8) {
		    switch(Integer.parseInt(t[8],10)) {
		    case -2:
			w.state = "WFSTATE_PLANNED";
			w.exitstatus = "";
			break;
		    case -1:
			w.state = "WFSTATE_RUNNING";
			w.exitstatus = "";
			break;
		    default:
			w.state = "WFSTATE_FINISHED";
			w.exitstatus = t[8];
			break;
		    }
		}
		if (nt > 9) w.mtime = t[9];

		if( nworkflows < (MAXWF) ) {
		    workflows[nworkflows++] = w;
		}
		else {
		    return false;
		}
	    }
	    rc = p.waitFor();
	    return true;
	} catch (Exception e) {
	    System.out.println("WorkflowJob.refresh: Exception: " + e.toString() );
	    return false;
	}
    }
   
    /* Instance Methods */
    
    /**
     * Sets the status fields in a Workflow instance. 
     * @return true if status was successfully obtained, false if not.
     */
    public boolean updateStatus()
    {
	boolean rc;

	long now = System.currentTimeMillis();
	if ( now > (timeOfLastRefresh + millisecsToRefreshStatus) ) { 
	    rc = Workflow.refresh();
	    rc = WorkflowJob.refresh();
	    timeOfLastRefresh = now;
	}

	for( int i=0; i<nworkflows; i++ ) {
	    if ( workflows[i].basedir.equals(basedir) && 
		 workflows[i].vogroup.equals(vogroup) &&
		 workflows[i].wfname.equals(wfname) &&
		 workflows[i].run.equals(run) ) {
		wfid = workflows[i].wfid;
		creator = workflows[i].creator;
		ctime = workflows[i].ctime;
		state = workflows[i].state;
		exitstatus = workflows[i].exitstatus;
		mtime = workflows[i].mtime;
		break;
	    }
	}

	if(wfid == null ) return false;

	//	System.out.println ("basedir = " + basedir + " vogroup = " + vogroup + " wfname = " + wfname + " run = " + run + " wfid = " + wfid + " creator = " + creator + " ctime = " + ctime + " state = " + state + " exitstatus = " + exitstatus + " mtime = " + mtime);

	jobs = new WorkflowJob[WFMAXJOBS];
	njobs = 0;
	for( int i=0; i<WorkflowJob.njobs; i++) {
	    if ( WorkflowJob.jobs[i].wfid.equals(wfid) )
		if ( njobs < WFMAXJOBS ) 
		    jobs[njobs++] = WorkflowJob.jobs[i];
	}
	return true;
    }

    public String toWFStatusString()
    {
	updateStatus();
	return 
	    "wfid=" + wfid +   
	    " run=" + run +
	    " ctime=" + ctime +
	    " state=" + state +
	    " exitstatus=" + exitstatus +
	    " mtime=" + mtime +
	    " refreshed=" + timeOfLastRefresh;
    }

    public String toJobStatusString()
    {
	updateStatus();
	if (njobs == 0) return "";
	String s = jobs[0].asStatusString();
	for( int i=1; i<njobs; i++ ) {
	    s += "\n" + jobs[i].asStatusString();
	}
	return(s);
    }

    public String toDetailStatusString()
    {
	return toWFStatusString() + "\n" + toJobStatusString();
    }

    public int stop()
    {
	return 0;
    }

    public int cleanup ()
    {
	return 0;
    }
}
